# Convert CSV files exported from Palm Datebook into iCAL format

# Written by Bernie Roehl (broehl@bernieroehl.com), July 2009

# Tested with version 6 of the Palm Desktop software, which stores its
# data in Microsoft Access databases.  You need to export the Datebook
# as a .dba file, then rename that to .mdb and double-click it to load
# it into Access.  Then export the Categories table into a file called
# "Datebook Categories.csv" and the Main table into "Datebook.csv".
# Be sure to check the box that produces a header line in the CSV file.
#
# Open up a command line and cd to the directory where those two csv
# files are.  Run this program and direct the output to a file with a
# ".ics" extension (the standard for iCal files).  For example:
#
#    python PalmDatebookToIcal.py >from_palm.ics
#
# That file ("from_palm.ics") can then be imported into any application
# that supports the iCal format.
#
# Only tested with Google Calendar, but if the .ics file doesn't import
# correctly into other applications you can "launder" it through Google
# Calendar (which can output iCal format files).

# Repeat codes in the Palm Datebook CSV file:

# Dn -- every n'th day  (e.g. D4 is every fourth day)
# Wn weekday -- every n'th weekday (e.g. W3 TH is every third Thursday)
# MDn dd -- every n'th month on the mm'th day (e.g. MD2 14 is the 14th day of every second month)
# MPn which+ weekday -- every n'th month on which'th weekday (e.g. MD4 3+ MO is the 3rd Monday of every fourth month)
# YMn monthnum -- every n'th year, in month monthnum (e.g. YM2 3 is the third month of every other year)

# The first field after the repeat code is either a "#" followed by an event count, or an end date in iCal foramt.
# This is followed by a semicolon-separated list of exceptions, which we currently do not process.

import sys, time
import csv

# First, read the datebook categories

categories = {}
d = csv.DictReader(open("Datebook Categories.csv"))
for r in d:
	categories[r["ID"]] = r["Name"]

# put out header

print "BEGIN:VCALENDAR\nVERSION:2.0\nMETHOD:PUBLISH"

# process the datebook file, one entry at a time

d = csv.DictReader(open("Datebook.csv"))
for r in d:
	start = time.localtime(int(r["Start Time"]))
	end = time.localtime(int(r["End Time"]))
	note, summary = r["Note"], r["Summary"]
	category = categories.get(r["Category"], "No category")

	print "BEGIN:VEVENT"
	if r["Untimed"] == "1":
		print "COMMENT: Untimed"
		print "DTSTART:%04d%02d%02d" % start[:3]
		print "DTEND:%04d%02d%02d" % time.localtime(int(r["Start Time"]) + 24*60*60)[:3]
	else:
		print "DTSTART:%04d%02d%02dT%02d%02d%02d" % start[:6]
		print "DTEND:%04d%02d%02dT%02d%02d%02d" % end[:6]

	print "SUMMARY:" + summary
	print "DESCRIPTION:" + note
	print "CATEGORIES:" + category

	if int(r["Alarm"]) and "noalarm" not in sys.argv:
		print "BEGIN:VALARM"
		print "TRIGGER:-P%dM" % int(r["Alarm Advance"])
		print "ACTION:DISPLAY"
		print "DESCRIPTION:" + summary
		print "END:VALARM"

	if r["Repeated Event"] != "":
		print "COMMENT: repeat rule was", r["Repeated Event"]

		repeat = r["Repeated Event"].split()

		# convert a Palm style "count" or "until" date into iCal format
		until = ""
		s = repeat[-1].split(',')[0]
		if s.startswith("#"):
			count = int(s[1:])
			if count:
				until = ";COUNT=%d" % count
		else:
			until = ";UNTIL=" + s

		if repeat[0].startswith("D"):
			interval = int(repeat[0][1:])
			print "RRULE:FREQ=DAILY;INTERVAL=%d%s" % (interval, until)
		elif repeat[0].startswith("W"):
			interval = int(repeat[0][1:])
			weekday = repeat[1]
			print "RRULE:FREQ=WEEKLY;INTERVAL=%d;BYDAY=%s;WKST=SU%s" % (interval, weekday, until)
		elif repeat[0].startswith("MD"):
			interval = int(repeat[0][2:])
			dd = int(repeat[1])
			print "RRULE:FREQ=MONTHLY;INTERVAL=%d;BYMONTHDAY=%s%s" % (interval, dd, until)
		elif repeat[0].startswith("MP"):
			interval = int(repeat[0][2:])
			if repeat[1].endswith("-"):
				sign = "-"
				which = int(repeat[1][:-1])
			elif repeat[1].endswith("+"):
				sign = "+"
				which = int(repeat[1][:-1])
			else:
				sign = "+"
				which = int(repeat[1])
			weekday = repeat[2]
			print "RRULE:FREQ=MONTHLY;INTERVAL=%d;BYDAY=%s%d%s;WKST=SU%s" % (interval, sign, which, weekday, until)
		elif repeat[0].startswith("YM"):
			interval = int(repeat[0][2:])
			month = int(repeat[1])
			print "RRULE:FREQ=YEARLY;INTERVAL=%d;BYMONTH=%d%s" % (interval, month, until)
		else:
			print >>sys.stder, "UNRECOGNIZED REPEAT", repeat

	print "END:VEVENT"

print "END:VCALENDAR"